Coverage Report

Created: 2026-04-26 08:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
D:\a\csshw\csshw\src\utils\windows.rs
Line
Count
Source
1
//! Windows API abstraction layer for console and system operations.
2
//!
3
//! This module provides a trait-based abstraction over Windows APIs to enable
4
//! mocking in tests and centralize Windows-specific functionality.
5
6
#![deny(clippy::implicit_return)]
7
#![allow(
8
    clippy::needless_return,
9
    clippy::doc_overindented_list_items,
10
    rustdoc::private_intra_doc_links
11
)]
12
13
use log::error;
14
use std::ffi::OsString;
15
use std::os::windows::ffi::OsStrExt;
16
use std::{mem, ptr};
17
18
use windows::core::{HSTRING, PCWSTR};
19
use windows::Win32::Foundation::{BOOL, COLORREF, FALSE, HANDLE, HWND, LPARAM, TRUE};
20
use windows::Win32::Graphics::Dwm::{DwmSetWindowAttribute, DWMWA_BORDER_COLOR};
21
use windows::Win32::System::Com::{CoCreateInstance, CLSCTX_ALL};
22
use windows::Win32::System::Console::{
23
    FillConsoleOutputAttribute, GetConsoleProcessList, GetConsoleScreenBufferInfo,
24
    GetConsoleWindow, GetStdHandle, ReadConsoleInputW, SetConsoleTextAttribute,
25
    CONSOLE_CHARACTER_ATTRIBUTES, CONSOLE_SCREEN_BUFFER_INFO, COORD, INPUT_RECORD, INPUT_RECORD_0,
26
    STD_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
27
};
28
use windows::Win32::System::Console::{GetConsoleMode, SetConsoleMode, CONSOLE_MODE};
29
use windows::Win32::System::Console::{
30
    ScrollConsoleScreenBufferW, SetConsoleCursorPosition, CHAR_INFO, KEY_EVENT as KEY_EVENT_U32,
31
    SMALL_RECT,
32
};
33
use windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
34
use windows::Win32::System::Threading::{
35
    CreateProcessW, CREATE_NEW_CONSOLE, PROCESS_INFORMATION, STARTUPINFOW,
36
};
37
use windows::Win32::System::Threading::{GetExitCodeProcess, OpenProcess};
38
use windows::Win32::UI::Accessibility::{CUIAutomation, IUIAutomation};
39
use windows::Win32::UI::WindowsAndMessaging::{
40
    EnumWindows, GetWindowTextW, GetWindowThreadProcessId, MoveWindow, SetWindowTextW,
41
    SYSTEM_METRICS_INDEX,
42
};
43
use windows::Win32::UI::WindowsAndMessaging::{
44
    GetForegroundWindow, GetWindowPlacement, SetForegroundWindow, ShowWindow, SHOW_WINDOW_CMD,
45
    WINDOWPLACEMENT,
46
};
47
48
#[cfg(test)]
49
use mockall::automock;
50
51
use super::constants::MAX_WINDOW_TITLE_LENGTH;
52
53
/// Trait for Windows API operations to enable mocking in tests.
54
///
55
/// This trait abstracts Windows API calls to allow for unit testing without
56
/// actual system interaction. All console and system operations should go
57
/// through this trait.
58
#[cfg_attr(test, automock)]
59
pub trait WindowsApi: Send + Sync {
60
    /// Sets the console window title.
61
    ///
62
    /// # Arguments
63
    ///
64
    /// * `title` - The string to be set as window title
65
    ///
66
    /// # Returns
67
    ///
68
    /// Result indicating success or failure of the operation
69
    fn set_console_title(&self, title: &str) -> windows::core::Result<()>;
70
71
    /// Gets the console window title as UTF-16 buffer.
72
    ///
73
    /// # Arguments
74
    ///
75
    /// * `buffer` - Mutable buffer to store the UTF-16 encoded title
76
    ///
77
    /// # Returns
78
    ///
79
    /// Number of characters copied to the buffer
80
    fn get_console_title(&self, buffer: &mut [u16]) -> i32;
81
82
    /// Gets OS version string.
83
    ///
84
    /// # Returns
85
    ///
86
    /// String representation of the OS version
87
    fn get_os_version(&self) -> String;
88
89
    /// Arranges the console window position and size.
90
    ///
91
    /// # Arguments
92
    ///
93
    /// * `x` - The x coordinate to move the window to
94
    /// * `y` - The y coordinate to move the window to
95
    /// * `width` - The width in pixels to resize the window to
96
    /// * `height` - The height in pixels to resize the window to
97
    ///
98
    /// # Returns
99
    ///
100
    /// Result indicating success or failure of the operation
101
    fn arrange_console(&self, x: i32, y: i32, width: i32, height: i32)
102
        -> windows::core::Result<()>;
103
104
    /// Sets console text attribute.
105
    ///
106
    /// # Arguments
107
    ///
108
    /// * `attributes` - Console character attributes to set
109
    ///
110
    /// # Returns
111
    ///
112
    /// Result indicating success or failure of the operation
113
    fn set_console_text_attribute(
114
        &self,
115
        attributes: CONSOLE_CHARACTER_ATTRIBUTES,
116
    ) -> windows::core::Result<()>;
117
118
    /// Gets console screen buffer info.
119
    ///
120
    /// # Returns
121
    ///
122
    /// Console screen buffer information or error
123
    fn get_console_screen_buffer_info(&self) -> windows::core::Result<CONSOLE_SCREEN_BUFFER_INFO>;
124
125
    /// Fills console output with specified attribute.
126
    ///
127
    /// # Arguments
128
    ///
129
    /// * `attribute` - Attribute to fill with
130
    /// * `length` - Number of characters to fill
131
    /// * `coord` - Starting coordinate
132
    ///
133
    /// # Returns
134
    ///
135
    /// Number of characters actually filled or error
136
    fn fill_console_output_attribute(
137
        &self,
138
        attribute: u16,
139
        length: u32,
140
        coord: COORD,
141
    ) -> windows::core::Result<u32>;
142
143
    /// Scrolls console screen buffer.
144
    ///
145
    /// # Arguments
146
    ///
147
    /// * `scroll_rect` - Rectangle to scroll
148
    /// * `scroll_target` - Target coordinate for scrolling
149
    /// * `fill_char` - Character to fill empty space with
150
    ///
151
    /// # Returns
152
    ///
153
    /// Result indicating success or failure of the operation
154
    fn scroll_console_screen_buffer(
155
        &self,
156
        scroll_rect: SMALL_RECT,
157
        scroll_target: COORD,
158
        fill_char: CHAR_INFO,
159
    ) -> windows::core::Result<()>;
160
161
    /// Sets console cursor position.
162
    ///
163
    /// # Arguments
164
    ///
165
    /// * `position` - New cursor position
166
    ///
167
    /// # Returns
168
    ///
169
    /// Result indicating success or failure of the operation
170
    fn set_console_cursor_position(&self, position: COORD) -> windows::core::Result<()>;
171
172
    /// Gets standard handle.
173
    ///
174
    /// # Arguments
175
    ///
176
    /// * `handle_type` - Type of standard handle to retrieve
177
    ///
178
    /// # Returns
179
    ///
180
    /// Handle to the requested standard device or error
181
    fn get_std_handle(&self, handle_type: STD_HANDLE) -> windows::core::Result<HANDLE>;
182
183
    /// Reads console input.
184
    ///
185
    /// # Arguments
186
    ///
187
    /// * `buffer` - Buffer to store input records
188
    ///
189
    /// # Returns
190
    ///
191
    /// Number of records read or error
192
    fn read_console_input(&self, buffer: &mut [INPUT_RECORD]) -> windows::core::Result<u32>;
193
194
    /// Sets DWM window attribute for border color.
195
    ///
196
    /// # Arguments
197
    ///
198
    /// * `color` - Color to set as border color
199
    ///
200
    /// # Returns
201
    ///
202
    /// Result indicating success or failure of the operation
203
    fn set_console_border_color(&self, color: &COLORREF) -> windows::core::Result<()>;
204
205
    /// Writes input records to the console input buffer.
206
    ///
207
    /// # Arguments
208
    ///
209
    /// * `buffer` - Input records to write
210
    /// * `number_written` - Mutable reference to store number of records written
211
    ///
212
    /// # Returns
213
    ///
214
    /// Result indicating success or failure of the operation
215
    fn write_console_input(
216
        &self,
217
        buffer: &[INPUT_RECORD],
218
        number_written: &mut u32,
219
    ) -> windows::core::Result<()>;
220
221
    /// Gets the last Windows error code.
222
    ///
223
    /// # Returns
224
    ///
225
    /// The last error code from Windows API
226
    fn get_last_error(&self) -> u32;
227
228
    /// Generates a console control event.
229
    ///
230
    /// # Arguments
231
    ///
232
    /// * `ctrl_event` - Control event type to generate
233
    /// * `process_group_id` - Process group ID to send event to
234
    ///
235
    /// # Returns
236
    ///
237
    /// Result indicating success or failure of the operation
238
    fn generate_console_ctrl_event(
239
        &self,
240
        ctrl_event: u32,
241
        process_group_id: u32,
242
    ) -> windows::core::Result<()>;
243
244
    /// Get standard output handle
245
    ///
246
    /// # Returns
247
    ///
248
    /// Handle to standard output or error
249
    fn get_stdout_handle(&self) -> windows::core::Result<HANDLE>;
250
251
    /// Get console screen buffer information
252
    ///
253
    /// # Arguments
254
    ///
255
    /// * `handle` - Handle to console screen buffer
256
    ///
257
    /// # Returns
258
    ///
259
    /// Console screen buffer information or error
260
    fn get_console_attached_process_count(&self) -> u32;
261
262
    /// Create a new process
263
    ///
264
    /// # Arguments
265
    ///
266
    /// * `application` - Application name including file extension
267
    /// * `args` - List of arguments to the application
268
    ///
269
    /// # Returns
270
    ///
271
    /// Process information if successful, None otherwise
272
1
    fn create_process_with_args(
273
1
        &self,
274
1
        application: &str,
275
1
        args: Vec<String>,
276
1
    ) -> Option<windows::Win32::System::Threading::PROCESS_INFORMATION> {
277
1
        let command_line = build_command_line(application, &args);
278
1
        let mut startupinfo = STARTUPINFOW {
279
1
            cb: mem::size_of::<STARTUPINFOW>() as u32,
280
1
            ..Default::default()
281
1
        };
282
1
        let mut process_information = PROCESS_INFORMATION::default();
283
1
        let mut cmd_line = command_line;
284
1
        let command_line_ptr = windows::core::PWSTR(cmd_line.as_mut_ptr());
285
286
1
        match self.create_process_raw(
287
1
            application,
288
1
            command_line_ptr,
289
1
            &mut startupinfo,
290
1
            &mut process_information,
291
1
        ) {
292
1
            Ok(()) => return Some(process_information),
293
0
            Err(_) => return None,
294
        }
295
1
    }
296
297
    /// Low-level process creation API call
298
    ///
299
    /// # Arguments
300
    ///
301
    /// * `application` - Application name
302
    /// * `command_line` - Command line string as PWSTR
303
    /// * `startup_info` - Startup information structure
304
    /// * `process_info` - Process information structure to fill
305
    ///
306
    /// # Returns
307
    ///
308
    /// Result indicating success or failure of the operation
309
    fn create_process_raw(
310
        &self,
311
        application: &str,
312
        command_line: windows::core::PWSTR,
313
        startup_info: &mut windows::Win32::System::Threading::STARTUPINFOW,
314
        process_info: &mut windows::Win32::System::Threading::PROCESS_INFORMATION,
315
    ) -> windows::core::Result<()>;
316
317
    /// Get window handle for process ID
318
    ///
319
    /// # Arguments
320
    ///
321
    /// * `process_id` - Process ID to find window for
322
    ///
323
    /// # Returns
324
    ///
325
    /// Window handle for the process
326
    fn get_window_handle_for_process(&self, process_id: u32) -> HWND;
327
328
    /// Gets the console window handle.
329
    ///
330
    /// # Returns
331
    ///
332
    /// Handle to the console window
333
    fn get_console_window(&self) -> HWND;
334
335
    /// Gets the foreground window handle.
336
    ///
337
    /// # Returns
338
    ///
339
    /// Handle to the foreground window
340
    fn get_foreground_window(&self) -> HWND;
341
342
    /// Sets the foreground window.
343
    ///
344
    /// # Arguments
345
    ///
346
    /// * `hwnd` - Handle to the window to set as foreground
347
    ///
348
    /// # Returns
349
    ///
350
    /// Result indicating success or failure of the operation
351
    fn set_foreground_window(&self, hwnd: HWND) -> windows::core::Result<()>;
352
353
    /// Gets console mode for the specified handle.
354
    ///
355
    /// # Arguments
356
    ///
357
    /// * `handle` - Handle to the console input buffer
358
    ///
359
    /// # Returns
360
    ///
361
    /// Console mode or error
362
    fn get_console_mode(&self, handle: HANDLE) -> windows::core::Result<CONSOLE_MODE>;
363
364
    /// Sets console mode for the specified handle.
365
    ///
366
    /// # Arguments
367
    ///
368
    /// * `handle` - Handle to the console input buffer
369
    /// * `mode` - Console mode to set
370
    ///
371
    /// # Returns
372
    ///
373
    /// Result indicating success or failure of the operation
374
    fn set_console_mode(&self, handle: HANDLE, mode: CONSOLE_MODE) -> windows::core::Result<()>;
375
376
    /// Gets the exit code of the specified process.
377
    ///
378
    /// # Arguments
379
    ///
380
    /// * `handle` - Handle to the process
381
    ///
382
    /// # Returns
383
    ///
384
    /// Exit code or error
385
    fn get_exit_code(&self, handle: HANDLE) -> windows::core::Result<u32>;
386
387
    /// Moves and resizes a window.
388
    ///
389
    /// # Arguments
390
    ///
391
    /// * `hwnd` - Handle to the window
392
    /// * `x` - New x position
393
    /// * `y` - New y position
394
    /// * `width` - New width
395
    /// * `height` - New height
396
    /// * `repaint` - Whether to repaint the window
397
    ///
398
    /// # Returns
399
    ///
400
    /// Result indicating success or failure of the operation
401
    fn move_window(
402
        &self,
403
        hwnd: HWND,
404
        x: i32,
405
        y: i32,
406
        width: i32,
407
        height: i32,
408
        repaint: bool,
409
    ) -> windows::core::Result<()>;
410
411
    /// Gets window placement information.
412
    ///
413
    /// # Arguments
414
    ///
415
    /// * `hwnd` - Handle to the window
416
    ///
417
    /// # Returns
418
    ///
419
    /// Window placement information or error
420
    fn get_window_placement(&self, hwnd: HWND) -> windows::core::Result<WINDOWPLACEMENT>;
421
422
    /// Shows a window in the specified state.
423
    ///
424
    /// # Arguments
425
    ///
426
    /// * `hwnd` - Handle to the window
427
    /// * `cmd_show` - Show command
428
    ///
429
    /// # Returns
430
    ///
431
    /// Result indicating success or failure of the operation
432
    fn show_window(&self, hwnd: HWND, cmd_show: SHOW_WINDOW_CMD) -> windows::core::Result<bool>;
433
434
    /// Focuses a window using UI Automation.
435
    ///
436
    /// # Arguments
437
    ///
438
    /// * `hwnd` - Handle to the window to focus
439
    ///
440
    /// # Returns
441
    ///
442
    /// Result indicating success or failure of the operation
443
    fn focus_window_with_automation(&self, hwnd: HWND) -> windows::core::Result<()>;
444
445
    /// Checks if a window handle is valid.
446
    ///
447
    /// # Arguments
448
    ///
449
    /// * `hwnd` - Handle to the window to check
450
    ///
451
    /// # Returns
452
    ///
453
    /// True if the window is valid, false otherwise
454
    fn is_window(&self, hwnd: HWND) -> bool;
455
456
    /// Opens a process with the specified access rights.
457
    ///
458
    /// # Arguments
459
    ///
460
    /// * `access` - Access rights for the process handle
461
    /// * `inherit` - Whether the handle can be inherited
462
    /// * `process_id` - Process ID to open
463
    ///
464
    /// # Returns
465
    ///
466
    /// Process handle or error
467
    fn open_process(
468
        &self,
469
        access: u32,
470
        inherit: bool,
471
        process_id: u32,
472
    ) -> windows::core::Result<HANDLE>;
473
474
    /// Initializes the COM library for use by the calling thread.
475
    ///
476
    /// # Arguments
477
    ///
478
    /// * `coinit` - Initialization options for the COM library
479
    ///
480
    /// # Returns
481
    ///
482
    /// Result indicating success or failure of the operation
483
    fn initialize_com_library(
484
        &self,
485
        coinit: windows::Win32::System::Com::COINIT,
486
    ) -> windows::core::Result<()>;
487
488
    /// Gets system metrics information.
489
    ///
490
    /// # Arguments
491
    ///
492
    /// * `index` - System metric index to retrieve
493
    ///
494
    /// # Returns
495
    ///
496
    /// The requested system metric value
497
    fn get_system_metrics(&self, index: SYSTEM_METRICS_INDEX) -> i32;
498
499
    /// Sets the process DPI awareness.
500
    ///
501
    /// # Arguments
502
    ///
503
    /// * `value` - DPI awareness value to set
504
    ///
505
    /// # Returns
506
    ///
507
    /// Result indicating success or failure of the operation
508
    fn set_process_dpi_awareness(
509
        &self,
510
        value: windows::Win32::UI::HiDpi::PROCESS_DPI_AWARENESS,
511
    ) -> windows::core::Result<()>;
512
}
513
514
#[cfg(test)]
515
impl Clone for MockWindowsApi {
516
0
    fn clone(&self) -> Self {
517
0
        return MockWindowsApi::new();
518
0
    }
519
}
520
521
/// Default implementation of WindowsApi that calls actual Windows APIs.
522
///
523
/// This implementation provides direct access to Windows system APIs and should
524
/// be used in production code. For testing, use the MockWindowsApi instead.
525
#[derive(Clone)]
526
pub struct DefaultWindowsApi;
527
528
#[cfg_attr(coverage_nightly, coverage(off))]
529
impl WindowsApi for DefaultWindowsApi {
530
    fn set_console_title(&self, title: &str) -> windows::core::Result<()> {
531
        return unsafe { SetWindowTextW(GetConsoleWindow(), &HSTRING::from(title)) };
532
    }
533
534
    fn get_console_title(&self, buffer: &mut [u16]) -> i32 {
535
        return unsafe { GetWindowTextW(GetConsoleWindow(), buffer) };
536
    }
537
538
    fn get_os_version(&self) -> String {
539
        return os_info::get().version().to_string();
540
    }
541
542
    fn arrange_console(
543
        &self,
544
        x: i32,
545
        y: i32,
546
        width: i32,
547
        height: i32,
548
    ) -> windows::core::Result<()> {
549
        return unsafe { MoveWindow(GetConsoleWindow(), x, y, width, height, true) };
550
    }
551
552
    fn set_console_text_attribute(
553
        &self,
554
        attributes: CONSOLE_CHARACTER_ATTRIBUTES,
555
    ) -> windows::core::Result<()> {
556
        return unsafe { SetConsoleTextAttribute(self.get_stdout_handle()?, attributes) };
557
    }
558
559
    fn get_console_screen_buffer_info(&self) -> windows::core::Result<CONSOLE_SCREEN_BUFFER_INFO> {
560
        let mut buffer_info = CONSOLE_SCREEN_BUFFER_INFO::default();
561
        unsafe { GetConsoleScreenBufferInfo(self.get_stdout_handle()?, &mut buffer_info)? };
562
        return Ok(buffer_info);
563
    }
564
565
    fn fill_console_output_attribute(
566
        &self,
567
        attribute: u16,
568
        length: u32,
569
        coord: COORD,
570
    ) -> windows::core::Result<u32> {
571
        let mut number_written = 0u32;
572
        unsafe {
573
            FillConsoleOutputAttribute(
574
                self.get_stdout_handle()?,
575
                attribute,
576
                length,
577
                coord,
578
                &mut number_written,
579
            )?
580
        };
581
        return Ok(number_written);
582
    }
583
584
    fn scroll_console_screen_buffer(
585
        &self,
586
        scroll_rect: SMALL_RECT,
587
        scroll_target: COORD,
588
        fill_char: CHAR_INFO,
589
    ) -> windows::core::Result<()> {
590
        return unsafe {
591
            ScrollConsoleScreenBufferW(
592
                self.get_stdout_handle()?,
593
                &scroll_rect,
594
                None,
595
                scroll_target,
596
                &fill_char,
597
            )
598
        };
599
    }
600
601
    fn set_console_cursor_position(&self, position: COORD) -> windows::core::Result<()> {
602
        return unsafe { SetConsoleCursorPosition(self.get_stdout_handle()?, position) };
603
    }
604
605
    fn get_std_handle(&self, handle_type: STD_HANDLE) -> windows::core::Result<HANDLE> {
606
        return unsafe { GetStdHandle(handle_type) };
607
    }
608
609
    fn read_console_input(&self, buffer: &mut [INPUT_RECORD]) -> windows::core::Result<u32> {
610
        let mut number_read = 0u32;
611
        unsafe {
612
            ReadConsoleInputW(
613
                self.get_std_handle(STD_INPUT_HANDLE)?,
614
                buffer,
615
                &mut number_read,
616
            )?
617
        };
618
        return Ok(number_read);
619
    }
620
621
    fn set_console_border_color(&self, color: &COLORREF) -> windows::core::Result<()> {
622
        return unsafe {
623
            DwmSetWindowAttribute(
624
                GetConsoleWindow(),
625
                DWMWA_BORDER_COLOR,
626
                color as *const COLORREF as *const _,
627
                mem::size_of::<COLORREF>() as u32,
628
            )
629
        };
630
    }
631
632
    fn write_console_input(
633
        &self,
634
        buffer: &[INPUT_RECORD],
635
        number_written: &mut u32,
636
    ) -> windows::core::Result<()> {
637
        unsafe {
638
            windows::Win32::System::Console::WriteConsoleInputW(
639
                GetStdHandle(STD_INPUT_HANDLE)?,
640
                buffer,
641
                number_written,
642
            )?
643
        };
644
        return Ok(());
645
    }
646
647
    fn get_last_error(&self) -> u32 {
648
        return unsafe { windows::Win32::Foundation::GetLastError().0 };
649
    }
650
651
    fn generate_console_ctrl_event(
652
        &self,
653
        ctrl_event: u32,
654
        process_group_id: u32,
655
    ) -> windows::core::Result<()> {
656
        return unsafe {
657
            windows::Win32::System::Console::GenerateConsoleCtrlEvent(ctrl_event, process_group_id)
658
        };
659
    }
660
661
    fn get_stdout_handle(&self) -> windows::core::Result<HANDLE> {
662
        return self.get_std_handle(STD_OUTPUT_HANDLE);
663
    }
664
665
    fn get_console_attached_process_count(&self) -> u32 {
666
        let mut value: [u32; 1] = [0];
667
        unsafe { return GetConsoleProcessList(&mut value) };
668
    }
669
670
    fn get_window_handle_for_process(&self, process_id: u32) -> HWND {
671
        /// Data structure for window search callback
672
        struct WindowSearchData {
673
            /// The process ID we're searching for
674
            target_process_id: u32,
675
            /// Mutable reference to store the found window handle
676
            found_handle: *mut Option<HWND>,
677
        }
678
679
        /// Callback function for finding windows by process ID with proper handle capture
680
        unsafe extern "system" fn find_window_callback_with_capture(
681
            hwnd: HWND,
682
            lparam: LPARAM,
683
        ) -> BOOL {
684
            let search_data = &mut *(lparam.0 as *mut WindowSearchData);
685
            let mut window_process_id: u32 = 0;
686
            GetWindowThreadProcessId(hwnd, Some(&mut window_process_id));
687
688
            if search_data.target_process_id == window_process_id {
689
                // Store the found window handle
690
                *search_data.found_handle = Some(hwnd);
691
                return FALSE; // Stop enumeration
692
            }
693
            return TRUE; // Continue enumeration
694
        }
695
696
        let mut found_handle = None;
697
        let mut search_data = WindowSearchData {
698
            target_process_id: process_id,
699
            found_handle: &mut found_handle,
700
        };
701
702
        loop {
703
            let _ = unsafe {
704
                EnumWindows(
705
                    Some(find_window_callback_with_capture),
706
                    LPARAM(&mut search_data as *mut WindowSearchData as isize),
707
                )
708
            };
709
            if let Some(handle) = found_handle {
710
                return handle;
711
            }
712
        }
713
    }
714
715
    fn create_process_raw(
716
        &self,
717
        application: &str,
718
        command_line: windows::core::PWSTR,
719
        startup_info: &mut windows::Win32::System::Threading::STARTUPINFOW,
720
        process_info: &mut windows::Win32::System::Threading::PROCESS_INFORMATION,
721
    ) -> windows::core::Result<()> {
722
        return unsafe {
723
            CreateProcessW(
724
                &HSTRING::from(application),
725
                Some(command_line),
726
                Some(ptr::null_mut()),
727
                Some(ptr::null_mut()),
728
                false,
729
                CREATE_NEW_CONSOLE,
730
                Some(ptr::null_mut()),
731
                PCWSTR::null(),
732
                ptr::addr_of_mut!(*startup_info),
733
                ptr::addr_of_mut!(*process_info),
734
            )
735
        };
736
    }
737
738
    fn get_console_window(&self) -> HWND {
739
        return unsafe { GetConsoleWindow() };
740
    }
741
742
    fn get_foreground_window(&self) -> HWND {
743
        return unsafe { GetForegroundWindow() };
744
    }
745
746
    fn set_foreground_window(&self, hwnd: HWND) -> windows::core::Result<()> {
747
        let result = unsafe { SetForegroundWindow(hwnd) };
748
        if result.as_bool() {
749
            return Ok(());
750
        } else {
751
            return Err(windows::core::Error::from_win32());
752
        }
753
    }
754
755
    fn get_console_mode(&self, handle: HANDLE) -> windows::core::Result<CONSOLE_MODE> {
756
        let mut mode = CONSOLE_MODE(0u32);
757
        unsafe { GetConsoleMode(handle, &mut mode)? };
758
        return Ok(mode);
759
    }
760
761
    fn set_console_mode(&self, handle: HANDLE, mode: CONSOLE_MODE) -> windows::core::Result<()> {
762
        return unsafe { SetConsoleMode(handle, mode) };
763
    }
764
765
    fn get_exit_code(&self, handle: HANDLE) -> windows::core::Result<u32> {
766
        let mut exit_code: u32 = 0;
767
        unsafe { GetExitCodeProcess(handle, &mut exit_code)? };
768
        return Ok(exit_code);
769
    }
770
771
    fn move_window(
772
        &self,
773
        hwnd: HWND,
774
        x: i32,
775
        y: i32,
776
        width: i32,
777
        height: i32,
778
        repaint: bool,
779
    ) -> windows::core::Result<()> {
780
        return unsafe { MoveWindow(hwnd, x, y, width, height, repaint) };
781
    }
782
783
    fn get_window_placement(&self, hwnd: HWND) -> windows::core::Result<WINDOWPLACEMENT> {
784
        let mut placement: WINDOWPLACEMENT = WINDOWPLACEMENT {
785
            length: mem::size_of::<WINDOWPLACEMENT>() as u32,
786
            ..Default::default()
787
        };
788
        unsafe { GetWindowPlacement(hwnd, &mut placement)? };
789
        return Ok(placement);
790
    }
791
792
    fn show_window(&self, hwnd: HWND, cmd_show: SHOW_WINDOW_CMD) -> windows::core::Result<bool> {
793
        let result = unsafe { ShowWindow(hwnd, cmd_show) };
794
        return Ok(result.as_bool());
795
    }
796
797
    fn focus_window_with_automation(&self, hwnd: HWND) -> windows::core::Result<()> {
798
        let automation: IUIAutomation =
799
            unsafe { CoCreateInstance(&CUIAutomation, None, CLSCTX_ALL)? };
800
        let window = unsafe { automation.ElementFromHandle(hwnd)? };
801
        unsafe { window.SetFocus()? };
802
        return Ok(());
803
    }
804
805
    fn is_window(&self, hwnd: HWND) -> bool {
806
        return unsafe { windows::Win32::UI::WindowsAndMessaging::IsWindow(Some(hwnd)).as_bool() };
807
    }
808
809
    fn open_process(
810
        &self,
811
        access: u32,
812
        inherit: bool,
813
        process_id: u32,
814
    ) -> windows::core::Result<HANDLE> {
815
        return unsafe { OpenProcess(PROCESS_ACCESS_RIGHTS(access), inherit, process_id) };
816
    }
817
818
    fn initialize_com_library(
819
        &self,
820
        coinit: windows::Win32::System::Com::COINIT,
821
    ) -> windows::core::Result<()> {
822
        let result = unsafe { windows::Win32::System::Com::CoInitializeEx(None, coinit) };
823
        if result.is_ok() {
824
            return Ok(());
825
        } else {
826
            return Err(windows::core::Error::from(result));
827
        }
828
    }
829
830
    fn get_system_metrics(&self, index: SYSTEM_METRICS_INDEX) -> i32 {
831
        return unsafe { windows::Win32::UI::WindowsAndMessaging::GetSystemMetrics(index) };
832
    }
833
834
    fn set_process_dpi_awareness(
835
        &self,
836
        value: windows::Win32::UI::HiDpi::PROCESS_DPI_AWARENESS,
837
    ) -> windows::core::Result<()> {
838
        return unsafe { windows::Win32::UI::HiDpi::SetProcessDpiAwareness(value) };
839
    }
840
}
841
842
/// u16 representation of a [KEY_EVENT][1].
843
///
844
/// For some reason the public [KEY_EVENT][1] constant is a u32
845
/// while the [INPUT_RECORD][2].`EventType` is u16...
846
///
847
/// [1]: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/Console/constant.KEY_EVENT.html
848
/// [2]: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/Console/struct.INPUT_RECORD.html
849
pub const KEY_EVENT: u16 = KEY_EVENT_U32 as u16;
850
851
/// Build command line string for Windows process creation
852
///
853
/// # Arguments
854
///
855
/// * `application` - Application name including file extension
856
/// * `args` - List of arguments to the application
857
///
858
/// # Returns
859
///
860
/// UTF-16 encoded command line with proper quoting
861
///
862
/// # Examples
863
///
864
/// ```
865
/// use csshw_lib::utils::windows::build_command_line;
866
///
867
/// let cmd_line = build_command_line("cmd.exe", &["arg1".to_string(), "arg2".to_string()]);
868
/// // Returns UTF-16 encoded: "cmd.exe" "arg1" "arg2"\0
869
/// ```
870
4
pub fn build_command_line(application: &str, args: &[String]) -> Vec<u16> {
871
4
    let mut cmd: Vec<u16> = Vec::new();
872
4
    cmd.push(b'"' as u16);
873
4
    cmd.extend(OsString::from(application).encode_wide());
874
4
    cmd.push(b'"' as u16);
875
876
5
    for arg in 
args4
{
877
5
        cmd.push(' ' as u16);
878
5
        cmd.push(b'"' as u16);
879
5
        cmd.extend(OsString::from(arg).encode_wide());
880
5
        cmd.push(b'"' as u16);
881
5
    }
882
4
    cmd.push(0); // add null terminator
883
884
4
    return cmd;
885
4
}
886
887
/// Sets the back- and foreground color of the current console window using the provided API.
888
///
889
/// # Arguments
890
///
891
/// * `api` - The Windows API implementation to use.
892
/// * `color` - The color value describing the back- and foreground color.
893
///
894
/// # Examples
895
///
896
/// ```no_run
897
/// use csshw_lib::utils::windows::{set_console_color, DefaultWindowsApi};
898
/// use windows::Win32::System::Console::CONSOLE_CHARACTER_ATTRIBUTES;
899
///
900
/// let api = DefaultWindowsApi;
901
/// set_console_color(&api, CONSOLE_CHARACTER_ATTRIBUTES(0x0F));
902
/// ```
903
2
pub fn set_console_color(api: &dyn WindowsApi, color: CONSOLE_CHARACTER_ATTRIBUTES) {
904
2
    api.set_console_text_attribute(color).unwrap();
905
2
    let buffer_info = api.get_console_screen_buffer_info().unwrap();
906
24
    for y in 
0..buffer_info.dwSize.Y2
{
907
24
        api.fill_console_output_attribute(
908
24
            color.0,
909
24
            buffer_info.dwSize.X.try_into().unwrap(),
910
24
            COORD { X: 0, Y: y },
911
24
        )
912
24
        .unwrap();
913
24
    }
914
2
}
915
916
/// Empties the console screen output buffer of the current console window using the provided API.
917
///
918
/// # Arguments
919
///
920
/// * `api` - The Windows API implementation to use.
921
///
922
/// # Examples
923
///
924
/// ```no_run
925
/// use csshw_lib::utils::windows::{clear_screen, DefaultWindowsApi};
926
///
927
/// let api = DefaultWindowsApi;
928
/// clear_screen(&api);
929
/// ```
930
2
pub fn clear_screen(api: &dyn WindowsApi) {
931
2
    let buffer_info = api.get_console_screen_buffer_info().unwrap();
932
2
    let scroll_rect = SMALL_RECT {
933
2
        Left: 0,
934
2
        Top: 0,
935
2
        Right: buffer_info.dwSize.X,
936
2
        Bottom: buffer_info.dwSize.Y,
937
2
    };
938
2
    let scroll_target = COORD {
939
2
        X: buffer_info.dwSize.X,
940
2
        Y: 0 - buffer_info.dwSize.Y,
941
2
    };
942
2
    let mut char_info = CHAR_INFO::default();
943
2
    char_info.Char.UnicodeChar = ' ' as u16;
944
2
    char_info.Attributes = buffer_info.wAttributes.0;
945
946
2
    api.scroll_console_screen_buffer(scroll_rect, scroll_target, char_info)
947
2
        .unwrap();
948
949
2
    let cursor_position = COORD { X: 0, Y: 0 };
950
2
    api.set_console_cursor_position(cursor_position).unwrap();
951
2
}
952
953
/// Sets the border color of the current console window using the provided APIs.
954
///
955
/// Windows10 does not support this.
956
///
957
/// # Arguments
958
///
959
/// * `api` - The Windows API implementation;
960
/// * `color` - RGB [COLORREF][1] to set as border color.
961
///
962
/// # Examples
963
///
964
/// ```no_run
965
/// use csshw_lib::utils::windows::{set_console_border_color, DefaultWindowsApi};
966
/// use windows::Win32::Foundation::COLORREF;
967
///
968
/// set_console_border_color(&DefaultWindowsApi, COLORREF(0x001A2B3C));
969
/// ```
970
///
971
/// [1]: https://learn.microsoft.com/en-us/windows/win32/gdi/colorref
972
3
pub fn set_console_border_color(api: &dyn WindowsApi, color: COLORREF) {
973
3
    if !is_windows_10(api) {
974
2
        api.set_console_border_color(&color).unwrap();
975
2
    
}1
976
3
}
977
978
/// Converts a UTF-16 buffer to a Rust String, filtering out null characters.
979
///
980
/// # Arguments
981
///
982
/// * `buffer` - The UTF-16 buffer to convert.
983
///
984
/// # Returns
985
///
986
/// The converted string.
987
///
988
/// # Examples
989
///
990
/// ```
991
/// use csshw_lib::utils::windows::utf16_buffer_to_string;
992
///
993
/// let utf16_data = vec![72, 101, 108, 108, 111, 0]; // "Hello" + null terminator
994
/// let result = utf16_buffer_to_string(&utf16_data);
995
/// assert_eq!(result, "Hello");
996
/// ```
997
5
pub fn utf16_buffer_to_string(buffer: &[u16]) -> String {
998
5
    let vec: Vec<u16> = buffer
999
5
        .iter()
1000
5
        .copied()
1001
49
        .
filter5
(|val| return *val != 0u16)
1002
5
        .collect();
1003
5
    return String::from_utf16(&vec).unwrap_or_else(|err| 
{0
1004
0
        error!("{}", err);
1005
0
        panic!("Failed to convert UTF-16 buffer to string, invalid utf16")
1006
    });
1007
5
}
1008
1009
/// Returns the title of the current console window using the provided API.
1010
///
1011
/// # Arguments
1012
///
1013
/// * `api` - The Windows API implementation to use.
1014
///
1015
/// # Returns
1016
///
1017
/// The title of the current console window.
1018
///
1019
/// # Examples
1020
///
1021
/// ```no_run
1022
/// use csshw_lib::utils::windows::{get_console_title, DefaultWindowsApi};
1023
///
1024
/// let title = get_console_title(&DefaultWindowsApi);
1025
/// println!("Console title: {}", title);
1026
/// ```
1027
0
pub fn get_console_title(api: &dyn WindowsApi) -> String {
1028
0
    let mut title: [u16; MAX_WINDOW_TITLE_LENGTH] = [0; MAX_WINDOW_TITLE_LENGTH];
1029
0
    api.get_console_title(&mut title);
1030
0
    return utf16_buffer_to_string(&title);
1031
0
}
1032
1033
/// Returns a [HANDLE] to the requested [STD_HANDLE] of the current process.
1034
///
1035
/// # Arguments
1036
///
1037
/// * `nstdhandle` - The standard handle to retrieve.
1038
///                  Either [STD_INPUT_HANDLE] or [STD_OUTPUT_HANDLE].
1039
///
1040
/// # Returns
1041
///
1042
/// The [HANDLE] to the requested [STD_HANDLE].
1043
#[cfg_attr(coverage_nightly, coverage(off))]
1044
fn get_std_handle(nstdhandle: STD_HANDLE) -> HANDLE {
1045
    return unsafe {
1046
        GetStdHandle(nstdhandle)
1047
            .unwrap_or_else(|_| panic!("Failed to retrieve standard handle: {nstdhandle:?}"))
1048
    };
1049
}
1050
1051
/// Returns a [HANDLE] to the [STD_INPUT_HANDLE] of the current process.
1052
///
1053
/// # Returns
1054
///
1055
/// Handle to the standard input of the current process.
1056
///
1057
/// # Examples
1058
///
1059
/// ```no_run
1060
/// use csshw_lib::utils::windows::get_console_input_buffer;
1061
///
1062
/// let input_handle = get_console_input_buffer();
1063
/// ```
1064
#[cfg_attr(coverage_nightly, coverage(off))]
1065
pub fn get_console_input_buffer() -> HANDLE {
1066
    return get_std_handle(STD_INPUT_HANDLE);
1067
}
1068
1069
/// Returns a [HANDLE] to the [STD_OUTPUT_HANDLE] of the current process.
1070
///
1071
/// # Returns
1072
///
1073
/// Handle to the standard output of the current process.
1074
///
1075
/// # Examples
1076
///
1077
/// ```no_run
1078
/// use csshw_lib::utils::windows::get_console_output_buffer;
1079
///
1080
/// let output_handle = get_console_output_buffer();
1081
/// ```
1082
#[cfg_attr(coverage_nightly, coverage(off))]
1083
pub fn get_console_output_buffer() -> HANDLE {
1084
    return get_std_handle(STD_OUTPUT_HANDLE);
1085
}
1086
1087
/// Returns a single [INPUT_RECORD] read from the current process stdinput using the provided API.
1088
///
1089
/// Blocks until 1 record was read.
1090
///
1091
/// # Arguments
1092
///
1093
/// * `api` - The Windows API implementation to use.
1094
///
1095
/// # Returns
1096
///
1097
/// A single INPUT_RECORD that was read.
1098
///
1099
/// # Examples
1100
///
1101
/// ```no_run
1102
/// use csshw_lib::utils::windows::{read_console_input, DefaultWindowsApi};
1103
///
1104
/// let api = DefaultWindowsApi;
1105
/// let input_record = read_console_input(&api);
1106
/// ```
1107
5
pub fn read_console_input(api: &dyn WindowsApi) -> INPUT_RECORD {
1108
    const NB_EVENTS: usize = 1;
1109
5
    let mut input_buffer: [INPUT_RECORD; NB_EVENTS] = [INPUT_RECORD::default(); NB_EVENTS];
1110
    loop {
1111
6
        let number_of_events_read = api
1112
6
            .read_console_input(&mut input_buffer)
1113
6
            .expect("Failed to read console input");
1114
6
        if number_of_events_read == NB_EVENTS as u32 {
1115
5
            break;
1116
1
        }
1117
    }
1118
5
    return input_buffer[0];
1119
5
}
1120
1121
/// Returns a single [INPUT_RECORD_0] where `EventType` == [`KEY_EVENT`] using the provided API.
1122
///
1123
/// Blocks until 1 key event record was read.
1124
///
1125
/// # Arguments
1126
///
1127
/// * `api` - The Windows API implementation to use.
1128
///
1129
/// # Returns
1130
///
1131
/// A single INPUT_RECORD_0 with EventType == KEY_EVENT.
1132
///
1133
/// # Examples
1134
///
1135
/// ```no_run
1136
/// use csshw_lib::utils::windows::{read_keyboard_input, DefaultWindowsApi};
1137
///
1138
/// let api = DefaultWindowsApi;
1139
/// let key_event = read_keyboard_input(&api);
1140
/// ```
1141
1
pub fn read_keyboard_input(api: &dyn WindowsApi) -> INPUT_RECORD_0 {
1142
    loop {
1143
2
        let input_record = read_console_input(api);
1144
2
        match input_record.EventType {
1145
            KEY_EVENT => {
1146
1
                return input_record.Event;
1147
            }
1148
            _ => {
1149
1
                continue;
1150
            }
1151
        }
1152
    }
1153
1
}
1154
1155
/// Changes size and position of the current console window using the provided API.
1156
///
1157
/// # Arguments
1158
///
1159
/// * `api` - The Windows API implementation to use.
1160
/// * `x`       - The x coordinate to move the window to.
1161
///               From the top left corner of the screen.
1162
/// * `y`       - The y coordinate to move the window to.
1163
///               From the top left corner of the screen.
1164
/// * `width`   - The width in pixels to resize the window to.
1165
///               In logical scaling.
1166
/// * `height`  - The height in pixels to resize the window to.
1167
///               In logical scaling.
1168
///
1169
/// # Examples
1170
///
1171
/// ```no_run
1172
/// use csshw_lib::utils::windows::{arrange_console, DefaultWindowsApi};
1173
///
1174
/// let api = DefaultWindowsApi;
1175
/// arrange_console(&api, 100, 100, 800, 600);
1176
/// ```
1177
0
pub fn arrange_console(api: &dyn WindowsApi, x: i32, y: i32, width: i32, height: i32) {
1178
    // FIXME: sometimes a daemon or client console isn't being arrange correctly
1179
    // when this simply retrying doesn't solve the issue. Maybe it has something to do
1180
    // with DPI awareness => https://docs.rs/embed-manifest/latest/embed_manifest/
1181
0
    api.arrange_console(x, y, width, height).unwrap();
1182
0
}
1183
1184
/// Detects if the current windows installation is Windows 10 or not using the provided API.
1185
///
1186
/// Uses the os version, Windows 10 is < `10._.22000`. Windows 11 started with build 22000.
1187
///
1188
/// # Arguments
1189
///
1190
/// * `api` - The Windows API implementation to use.
1191
///
1192
/// # Returns
1193
///
1194
/// Whether the current windows installation is Windows 10 or not.
1195
///
1196
/// # Examples
1197
///
1198
/// ```no_run
1199
/// use csshw_lib::utils::windows::{is_windows_10, DefaultWindowsApi};
1200
///
1201
/// if is_windows_10(&DefaultWindowsApi) {
1202
///     println!("Running on Windows 10");
1203
/// } else {
1204
///     println!("Running on Windows 11 or newer");
1205
/// }
1206
/// ```
1207
10
pub fn is_windows_10(api: &dyn WindowsApi) -> bool {
1208
10
    let version = api.get_os_version();
1209
10
    let mut iter = version.split('.');
1210
10
    let (major, _, build) = (
1211
10
        iter.next().unwrap().parse::<usize>().unwrap(),
1212
10
        iter.next().unwrap().parse::<usize>().unwrap(),
1213
10
        iter.next().unwrap().parse::<usize>().unwrap(),
1214
10
    );
1215
10
    return major < 10 || (
major == 109
&&
build < 220007
);
1216
10
}
1217
1218
#[cfg(test)]
1219
#[path = "../tests/utils/test_windows.rs"]
1220
mod test_mod;